import {Cask} from 'cascadium';
import {Voucher, Accrual} from 'cascadium-ledger';

import conform from './conform';

export const formCashflowRulesMain = (cashflowTable, priorityList, cashflowRulesEntry) => {

  const cashflowEndMap = cashflowTable.succ
    .unnest(({succ}) => succ)
    .grip('curr.name')
    .map(([k, [v]]) => [k, v])
    .getDict();

  const priorityMap = Object.fromEntries(priorityList.map((spec) => [spec.name, spec]));

  const rules = new Cask();
  
  for (let i = 0; i < cashflowRulesEntry.length; i++) {
    const entry = {...cashflowRulesEntry[i]};
    entry.priority = priorityMap[entry.cate];
    entry.dr = cashflowEndMap[entry.dr];
    entry.cr = cashflowEndMap[entry.cr];
    rules.push(entry);

    if (entry.priority === undefined) {
      console.log('UNDEF', entry);
    }
  }

  return rules;
}

export const formCashflowRulesAdditional = (cashflowTable, cashflowRulesEntry) => {

  const cashflowEndMap = cashflowTable.succ
    .unnest(({succ}) => succ)
    .grip('curr.name')
    .map(([k, [v]]) => [k, v])
    .getDict();

  const rules = new Cask();
  
  for (let i = 0; i < cashflowRulesEntry.length; i++) {
    const entry = {...cashflowRulesEntry[i]};
    entry.entryDest = cashflowEndMap[entry.entryDest];
    rules.push(entry);
  }

  return rules;
}

export const assignCashflowMain = (universal, journal, cashflowRules, cashflowTable) => {

  
  const [uncated, unrecog] = cashflowTable.succ.find(({curr:{code}}) => code === '0000').succ;
  

  const endmostCates = universal
    .unnestTo()
    .all('attr.level', 'END_CATE');

  const endCashflowTable = cashflowTable
    .unnestTo()
    .all('succ.length', 0);

  for (let cate of endmostCates) for (let rule of cashflowRules) {

    const pathPattern = rule.name.endsWith('/*') ? `${rule.name}*` : `${rule.name}/**`;

    if (conform(cate.path, pathPattern)){
      cate.attr.rule = rule;
    }
  }

  // 先进行准资金类科目中，需要认定货币资金发生性质的科目。
  // 
  // 如果科目规则中包含realloc属性，则意味着这个科目中所有对方科目类型为realloc的发生额，需要
  // 按照此科目所有其他对方科目重新分配。方法如下：
  // 
  // 1. 首先将此科目中所有类型为realloc（现金类）的对方科目的发生额归集到一起。如
  // 
  //    预付账款/公司A - 库存现金 借1000 贷1000
  //    预付账款/公司A - 某某支行 借1000 贷1000
  // 
  //    得到总的对货币资金的发生额 借2000 贷2000
  // 
  // 2. 获得此科目对其他类型的对方科目的发生额 如
  // 
  //    预付账款/公司A - 固定资产 借200 贷400
  //    预付账款/公司A - 存货　　 借50  贷50
  // 
  //    此处需要注意的是，这里是借贷方的发生额汇总到了一起，但是我们必须分别处理借方和贷方发生额
  //    使之成为
  // 
  //    预付账款/公司A - 固定资产 借200 贷0
  //    预付账款/公司A - 固定资产 借0   贷400
  //    预付账款/公司A - 存货　　 借50  贷0
  //    预付账款/公司A - 存货　　 借0   贷50
  // 
  //    因为我们需要通过借贷方向确定对应的现流表条目，才能进而确定货币资金将要归入的现流表条目。我们
  //    从对货币资金的发生额中按优先次序扣除固定资产和存货的发生额，得到
  // 
  //    预付账款/公司A - 库存现金 借0   贷200 （按出售固定资产认定，即固定资产贷方发生对应的现流条目）
  //    预付账款/公司A - 库存现金 借200 贷0   （按购买固定资产认定，即固定资产借方发生对应的现流条目）
  //    预付账款/公司A - 库存现金 借0   贷50   (按出售存货认定，即存货贷方发生对应的现流条目)
  //    预付账款/公司A - 库存现金 借50  贷0    (按购买存货认定，即存货借方发生对应的现流条目)
  // 
  //    再分别归入对应的现流条目中去。
  // 
  // 需要注意，已经汇总并重新分配的准资金对货币资金的发生，不应该在后续的逐笔分录的分析中出现。因此如果
  // 后续的分录中出现了借准资金类贷货币资金，或借货币资金贷准资金的情形，准资金类科目的发生额不再参与计算。
  // 但是对其他科目的发生不受影响，例如借准资金贷固定资产，都是在逐笔分析中分配到现流表中的。

  
  const quasiMonetaryCates = endmostCates.filter(({curr}) => curr.ref.attr.rule?.realloc);

  // The remaining quasi monetary will be assigned to fixed Asset. It will be seen as
  // buying or selling fixed Asset.
  const fixedAssetRule = cashflowRules.find(({name}) => name === '固定资产');

  for (let cate of quasiMonetaryCates) {
    const realloc = cate.curr.ref.attr.rule.realloc;

    // 如果标记为需要认定货币资金方向，则进行以下流程。
    if (realloc) {
      let monetary = new Cask();
      let inventory = new Cask();

      // 首先将此准货币资金科目的总发生额按对方科目划分，并汇总对货币资金的发生额。
      for (let destCate of cate.unnestTo().all('attr.level', 'DIRECTION')) {
        if (realloc === destCate.dest.ref.attr.rule?.cate) {
          for (let i = 0; i < destCate.succ.length; i++) {
            destCate.succ[i].assignedMonetary = true;
          }
          monetary.push(destCate);
        } else {
          inventory.push(destCate);
        }
      }

      // 如果准货币资金的科目并没有对货币资金的发生额，也就意味着不需要任何认定，那
      // 么就在这里退出了。
      if (monetary.length === 0) {
        continue;
      }

      inventory.orderBy('curr.ref.attr.rule.priority.code');

      const monetarySum = Voucher.from(monetary);
      
      const {decomp, rest} = monetarySum.decomposeAgainst(inventory);
      
      for (let i = 0; i < decomp.length; i++) {
        decomp[i].desc = `按 ${inventory[i].dest.ref.path} 科目认定的货币资金发生`;
        decomp[i].attr.style = 'virtual';
        decomp[i].attr.level = 'ENTRY';
        decomp[i].designatedMonetary = true;
        const dir = decomp[i].accrual.dir();
        const dest = inventory[i].dest.ref.attr.rule?.[dir];
        if (dest === undefined) {
          decomp[i].move(unrecog, {withProto: false, withPath: false, pushSide: 'TAIL'});
        } else {
          decomp[i].move(dest, {withProto: false, withPath: false, pushSide: 'TAIL'});
        }
      }

      const restDir= rest.accrual.dir();
      const restDest = inventory.length > 0 
        ? inventory[0].dest.ref
        : endmostCates.find(({curr:{ref}}) => ref.path.includes('固定资产'));

      rest.desc = `按 ${restDest.path} 科目认定的货币资金发生`;
      rest.attr.style = 'virtual';
      rest.attr.level = 'ENTRY';
  
      if (['cr', 'dr'].includes(restDir)) {

        if (!restDest.attr.rule){
          console.log(restDest, 'NO RULE');
        }
        const cashflowDest = (restDest.attr.rule ?? fixedAssetRule)[restDir];
        rest.move(cashflowDest, {withProto: false, withPath: false, pushSide: 'TAIL'});
        rest.designatedMonetary = true;

      } else if (restDir === 'both'){
        
        const {cr, dr} = rest.accrual;
        const restDr = rest.copy();
        const restCr = rest.copy();
        restDr.accrual = new Accrual({dr});
        restCr.accrual = new Accrual({cr});

        const {cr: destCr, dr: destDr} = restDest.attr.rule;
        restDr.move(destDr, {withProto: false, withPath: false, pushSide: 'TAIL'});
        restCr.move(destCr, {withProto: false, withPath: false, pushSide: 'TAIL'});
        restCr.designatedMonetary = true;
        restDr.designatedMonetary = true;
      }
    } else {
      console.warn(cate);
    }
  }
  
  for (let i = 0; i < journal.length; i++) {
    if (journal[i].succ.length % 2 !== 0) {
      console.warn('ODD ENTRIES');
    }
    const pairs = journal[i].succ.partition(2);
    for (let p = 0; p < pairs.length; p++) {
      
      const [head, tail] = pairs[p];
      
      const headRule = head.curr.ref.attr.rule;
      const tailRule = tail.curr.ref.attr.rule;

      if(!headRule && !tailRule) {
        // 如果两分录的规则均不存在，则均归入未识别科目
        head.move(unrecog, {withPath: false});
        tail.move(unrecog, {withPath: false});

        head.assignedMain = true;
        tail.assignedMain = true;

      } else if (!headRule || !tailRule){
        // 如果两分录中有一个未找到规则，则未找到规则的归入未识别科目，另一个需要具体判断
        const [unrecogEntry, rest] = (headRule === undefined) ? [head, tail] : [tail, head];
        const restRule = (headRule === undefined) ? tailRule : headRule;
        
        unrecogEntry.move(unrecog, {withPath: false});

        if (restRule.nonAlloc) {
          // 如果找到规则的分录，其规则标注为nonAlloc（不分配/货币资金）则被归入uncated
          rest.move(uncated, {withPath: false});
        } else {
          // 否则，归入规则所对应的现流表条目中。
          rest.move(restRule[rest.accrual.dir()], {withPath: false});
        }

        unrecogEntry.assignedMain = true;
        rest.assignedMain = true;

      } else if (headRule.nonAlloc && tailRule.nonAlloc){
        // 接下来是两分录的规则均存在的情况。如果两方同时为货币资金/nonAlloc，则均归入uncated
        head.move(uncated, {withPath: false});
        tail.move(uncated, {withPath: false});
        
        head.assignedMain = true;
        tail.assignedMain = true;

      } else if (headRule.nonAlloc || tailRule.nonAlloc) {
        // 如果有一方为货币资金/nonAlloc，则货币资金归入uncated，另一方需要具体判断
        const [uncatedEntry, rest] = headRule.nonAlloc ? [head, tail] : [tail, head];
        const [nonRule, restRule] = headRule.nonAlloc ? [headRule, tailRule] : [tailRule, headRule];

        uncatedEntry.move(uncated, {withPath: false});

        // rest.move(restRule[rest.accrual.dir()], {withPath: false});
        if (restRule.realloc === nonRule.cate) {
          // 如果非货币资金一方是准资金类，且被标记为realloc，说明这些分录在上一步已经计算过了
          // 此处不应该再参与计算。我们将其累加到quasiFromEntries作为验证。
        } else {
          // 否则按照rest的规则进行分配
          rest.move(restRule[rest.accrual.dir()], {withPath: false});
          rest.assignedMain = true;
        }

        uncatedEntry.assignedMain = true;

      } else {

        const dest = headRule.priority.code < tailRule.priority.code
          ? headRule[head.accrual.dir()]
          : headRule.priority.code > tailRule.priority.code
          ? tailRule[tail.accrual.dir()]
          : headRule['dr']

        head.move(dest, {withPath : false});
        tail.move(dest, {withPath : false});

        head.assignedMain = true;
        tail.assignedMain = true;
      }
    }
  }

  for (let endCash of endCashflowTable) {
    if (endCash.succ.length > 0) {
      const gathered = endCash.succ
        .grip(({curr, dest, accrual}) => `${curr.code}-${dest.code}-${accrual.dir()}`)
        .grap(([, entries]) => {
          const entry = Voucher.from(entries, {voucherID: undefined});
          entry.attr.level = 'DIRECTION';
          return entry;
        })
        .grip('curr.code')
        .grap(([, entries]) => entries)
        .flat()

      endCash.succ = new Cask();
      for (let i = 0; i < gathered.length; i++) {
        gathered[i].move(endCash, {withPath: false});
      }
    }
  }

}

export const assignCashflowAdditional = (cashflowTable, cashflowRules, universal) => {

  const profit = {
    OPERATIONAL: new Cask(),
    INVESTMENT: new Cask(),
    FINANCING: new Cask(),
    ADJUSTMENT: new Cask(),
    OVERALL: new Cask(),
  }

  cashflowTable.succ
    .find(({curr:{code}}) => code === '1001')
    .trav(entry => {
      const {attr:{level}, curr:{name}} = entry;
      if (level ==='ENTRY') {
        if (name !== '本年利润') {
          profit.OPERATIONAL.push(entry);
        } else {
          profit.OVERALL.push(entry);
        }
      }
    })

  const tempAcc = new Accrual();
  for (let i = 0; i < profit.OPERATIONAL.length; i++) {
    tempAcc.add(profit.OPERATIONAL[i].accrual);
  }
  for (let i = 0; i < profit.OVERALL.length; i++) {
    tempAcc.add(profit.OVERALL[i].accrual);
  }
  console.log(tempAcc, cashflowTable.succ.find(({curr:{code}}) => code === '1001').accrual, 'TEMP ACCRUAL');
    
  cashflowTable.succ
    .find(({curr:{code}}) => code === '2001')
    .trav(entry => {
      const {attr:{level}, curr:{name}} = entry;
      if (level ==='ENTRY' && name === '本年利润') {
        profit.INVESTMENT.push(entry);
        profit.OVERALL.push(entry);
      }
    })
  
  cashflowTable.succ
    .find(({curr:{code}}) => code === '3001')
    .trav(entry => {
      const {attr:{level}, curr:{name}} = entry;
      if (level ==='ENTRY' && name === '本年利润') {
        profit.FINANCING.push(entry);
        profit.OVERALL.push(entry);
      }
    });

  cashflowTable.succ
    .find(({curr:{code}}) => code === '6001')
    .trav(entry => {
      const {attr:{level}, curr:{name}} = entry;
      if (level ==='ENTRY' && name === '本年利润') {
        profit.ADJUSTMENT.push(entry);
        // profit.OVERALL.push(entry);
      }
    });

  // const accTotal = new Accrual();
  // for (let [type, entries] of Object.entries(profit)) {
  //   const acc = new Accrual();
  //   for (let i = 0; i < entries.length; i++) {
  //     acc.add(entries[i].accrual);
  //   }
  //   console.log(type, acc);
  //   accTotal.add(acc);
  // }

  for (let [key, entries] of Object.entries(profit)) {

    const ruleSet = cashflowRules.filter(({from}) => from === key);

    if (ruleSet.length === 0) {
      console.log('SECONDARY HANDLING',key, 'SKIPPED');
      continue;
    }
    console.log('SECONDARY HANDLING',key);

    const {entryDest: defDest} = ruleSet.find(({def}) => def);

    while (entries.length > 0) {
      const entry = entries.pop();
      
      const prime = entry.curr.ref.attr.rule;

      let assigned = false;
      for (let r = 0; r < ruleSet.length; r++) {
        const {name, cate, entryDest: dest} = ruleSet[r];
        if ((cate && prime.cate === cate) || (name === prime.name)) {
          entry.move(dest, {withProto:false, pushSide: 'TAIL', withPath: false});
          assigned = true;
          break;
        }
      }
      if (!assigned) {
        entry.move(defDest, {withProto:false, pushSide:'TAIL', withPath: false});
      }
    }

    for (let {entryDest: dest, type} of ruleSet) {

      const gatheredByDest = dest.succ
        .grip(({dest, accrual}) => `${dest}-${accrual.dir()}`)
        .grap(([, entries]) => {
          const voucher = Voucher.from(entries, {voucherID: undefined})
          voucher.attr.level = 'DIRECTION';
          return voucher;
        })

      dest.succ = gatheredByDest;

      dest.accrual = new Accrual();
      for (let i = 0; i < dest.succ.length; i++) {
        if (type === 'ADD') {
          // dest.accrual.add(dest.succ[i].accrual);
        } else if (type === 'SUB') {
          // dest.accrual.add(dest.succ[i].accrual.oppo());
          dest.succ[i].accrual = dest.succ[i].accrual.oppo(); 
        }
      }
    }
  }
}
